# _*_ coding: utf-8 _*_
"""
Time:     2024/8/22
Author:   2051261
File:     modify.py
"""
import re
import hashlib
import logging
from enum import Enum
import json
from typing import Dict, Any, List
from bs4 import BeautifulSoup
import crcmod

from extractor.utils.common_func import list_elements_to_lowercase
from extractor.const import ConstKey


logger = logging.getLogger(__name__)


class ModifyElement(Enum):
    ADD_URL_PREFIX = "add_str_prefix"
    STRIP_SPACE = "strip_space"
    RE_REPLACE = "re_replace"
    SIMPLE_HTML_PARSE = "simple_html_parse"
    ADD_ZNZ_MD = 'add_znz_md'
    VALUE_IN_LIST_AND_SET_VALUE = "value_in_list_and_set_value"
    ADD_MD5 = "add_md5"
    CONVERT_LATIN1_TO_GBK = "convert_latin1_to_gbk"


def is_html_string(data):
    pattern = re.compile(r'<[^>]+>')
    return bool(pattern.search(data))


def strip_space(data: Dict):
    for key, value in data.items():
        if isinstance(data[key], str):
            data[key] = data[key].strip()


def add_str_prefix(data: Dict, str_prefix: str, columns: list[str]):
    columns = list_elements_to_lowercase(columns)
    for col in columns:
        if col not in data and col.lower() not in data:
            raise KeyError(f'Column {col} not in data')
        data[col] = f"{str_prefix}{data[col]}" if data[col] else ''


def re_replace(data: Dict[str, Any], pattern: str, repl: str, columns: List) -> Dict[str, Any]:
    """
    正则替换
    :param data: 数据源
    :param pattern: 正则匹配的字符串
    :param repl: 替换的字符串列表
    :param columns: 修改的列
    :return:
    """
    columns = list_elements_to_lowercase(columns)
    for col in columns:
        if data[col] is not None:
            data[col] = re.sub(pattern, repl, data[col])
    return data


def simple_html_parse(data, parse_column: str, tag: str = None, tag_column: str = None,
                      parse_column_default_value=None, element_key=None, text_column=None,
                      separator=None, not_html_columns: List = None, not_html_value: List = None):
    """
    :param data: 源数据
    :param parse_column: 需要解析的列
    :param tag: 解析的标签
    :param tag_column:  保存解析后数据的key
    :param parse_column_default_value: 解析后源数据列值，None 时不需要填充
    :param element_key: 解析成 html 元素的后的key
    :param text_column: 文本列
    :param separator: 文本分割符
    :param not_html_columns: 不是 html 时需要设置的列
    :param not_html_value: 不是 html 时需要设置列的值
    :return:
    """
    if is_html_string(data[parse_column.lower()]):
        soup = BeautifulSoup(data[parse_column.lower()], "html.parser")
        tag_element = soup if tag is None else soup.find(tag)
        if tag_column:
            data[tag_column.lower()] = tag_element if element_key is None else tag_element.get(element_key)
        if text_column is not None:
            data[text_column.lower()] = (
                tag_element.text
                if separator is None
                else tag_element.get_text(separator=separator)
            )
        if parse_column_default_value is not None:
            data[parse_column.lower()] = parse_column_default_value
    else:
        if not_html_columns is not None:
            for column, value in zip(not_html_columns, not_html_value):
                data[column.lower()] = value


def str_slice(data: Dict, start: int, end: int, column):
    data[column.lower()] = data[column.lower()][start: end]


def add_znz_md(data: Dict, znz_md: str = 'znz_md', md_columns: List | None = None, pop_columns: List | None = None):
    md_data = {}

    if md_columns:
        md_columns = list_elements_to_lowercase(md_columns)
        for col in md_columns:
            md_data[col] = data[col]
    elif pop_columns:
        pop_columns = list_elements_to_lowercase(pop_columns)
        md_data = {
            k: v
            for k, v in data.items()
            if k not in pop_columns
        }
    else:
        for key, val in data.items():
            md_data[key] = val

    for key, value in md_data.items():
        if not isinstance(value, str):
            md_data[key] = str(value)
    # 将数据序列化为 JSON 字符串
    d = json.dumps(md_data).encode('utf-8')

    # 创建 CRC64 查找表，使用 ISO 3309 (也称为 ECMA)
    crc64_func = crcmod.mkCrcFun(0x142F0E1EBA9EA3693, initCrc=0, xorOut=0xFFFFFFFFFFFFFFFF)

    # 计算 CRC64 校验和
    crc_num = crc64_func(d)
    data[znz_md] = str(crc_num)


def value_in_list_and_set_value(data: Dict, columns: list, in_values: list, value: str):
    """
    列值在配置的列表元素中,设置为设置的值
    :param data:
    :param columns: 需要检查修改的列
    :param in_values: 配置的值列表
    :param value: 设置的默认值
    :return:
    """
    columns = list_elements_to_lowercase(columns)
    for col in columns:
        if data[col] in in_values:
            data[col] = value


def add_md5(data: Dict, md5_from_columns: list[str], md5_column: str):
    """
    添加 MD5 列
    :param data:
    :param md5_from_columns: 取哪些列为 MD5 输入
    :param md5_column: MD5 列的名称
    :return:
    """
    md5_from_columns = list_elements_to_lowercase(md5_from_columns)
    md5_hash = hashlib.md5()

    input_str = "".join([
        str(data[col] if col in data else data[col.lower()])
        for col in md5_from_columns
        if col in data or col.lower() in data
    ])

    md5_hash.update(input_str.encode('utf-8'))
    data[md5_column.lower()] = md5_hash.hexdigest()


def convert_latin1_to_gbk(data: Dict, columns: list[str]) -> dict:
    for col in columns:
        col_lower = col.lower()
        if col_lower in data:
            try:
                # 尝试进行 latin1 -> gbk 的编码解码
                data[col_lower] = data[col_lower].encode('latin1').decode('gbk')
            except (UnicodeEncodeError, UnicodeDecodeError) as e:
                # 如果编码解码失败，保持原始值，并输出错误信息
                print(f"Error encoding/decoding key '{col_lower}': {e}, using original value")
        else:
            # 抛出键不存在的异常
            raise KeyError(f"key: {col_lower} not in data: {data}")
    return data


def modify_element(data: Dict, modify_list: List[Dict[str, Any]]) -> Dict:
    """

    :param data: 数据源
    :param modify_list: 修改的函数列表
        [
            {
                "operate_func": "value_in_list_and_set_value",  # 检查使用的函数
                "kwargs": {  # 必要参数
                    "columns": []
                    "in_values": ["-", ""],
                    "value": "mark_column",

                }
            },
            {
                "operate_func": "add_znz_md",  #
                "kwargs": {
                    "znz_md": 'znz_md',
                    "md_columns": [],
                    pop_columns: []
                }
            }
        ]
    :return:
    """
    modify_functions = {
        ModifyElement.ADD_URL_PREFIX: add_str_prefix,
        ModifyElement.STRIP_SPACE: strip_space,
        ModifyElement.RE_REPLACE: re_replace,
        ModifyElement.SIMPLE_HTML_PARSE: simple_html_parse,
        ModifyElement.ADD_ZNZ_MD: add_znz_md,
        ModifyElement.VALUE_IN_LIST_AND_SET_VALUE: value_in_list_and_set_value,
        ModifyElement.ADD_MD5: add_md5,
        ModifyElement.CONVERT_LATIN1_TO_GBK: convert_latin1_to_gbk,
    }

    for item in modify_list:
        kwargs = item["kwargs"]
        operate_func = ModifyElement(item["operate_func"])  # 将 operate_func 转换为 CheckType 枚举
        modify_func = modify_functions.get(operate_func)
        if modify_func is None:
            raise ValueError(f"Unsupported modifyType: {operate_func}")
        modify_func(data, **kwargs)

    return {ConstKey.DATA: data}
